package cn.kinoko.common.aspect;

import cn.kinoko.common.aspect.annotation.CacheEvict;
import cn.kinoko.common.constant.HeaderConstant;
import cn.kinoko.common.utils.SpelUtil;
import cn.kinoko.common.utils.ThreadLocalUtil;
import com.github.benmanes.caffeine.cache.Cache;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * 分布式缓存删除切面
 *
 * @author kk
 */
@Slf4j
@Aspect
@Component
public class CacheEvictAspect {

    @Resource
    private Cache<String, Object> cache;

    @Around("@annotation(cacheEvict)")
    public Object around(ProceedingJoinPoint joinPoint, CacheEvict cacheEvict) throws Throwable {
        // 执行业务方法
        Object target = joinPoint.proceed();
        // 获取redisKey
        String redisKey = SpelUtil.getRedisKey(joinPoint, cacheEvict.key(), cacheEvict.params());
        // 如果是当前用户
        if (cacheEvict.currentUser()) {
            Long uid = (Long) ThreadLocalUtil.get(HeaderConstant.UID_HEADER);
            if (uid != null) redisKey = redisKey + ":" + uid;
        }
        // 删除缓存
        delKey(joinPoint, cacheEvict, redisKey);
        log.info("[caffeine]cache evict: {}", redisKey);
        // 双删
        if (cacheEvict.waitTime() != -1) {
            // 提出waitTime防止注解对象在切面执行后被回收导致空指针
            int waitTime = cacheEvict.waitTime();
            // 异步删除缓存
            String finalRedisKey = redisKey;
            CompletableFuture.runAsync(() -> {
                // 等待时间
                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(waitTime));
                delKey(joinPoint, cacheEvict, finalRedisKey);
            });
        }
        return target;
    }

    /**
     * 删除缓存
     * @param joinPoint 业务方法
     * @param cacheable 缓存注解
     * @param redisKey redisKey
     */
    private void delKey(ProceedingJoinPoint joinPoint, CacheEvict cacheable, String redisKey) {
        // 检查条件，删除缓存
        if (!SpelUtil.getBoolean(joinPoint, cacheable.unless(), false)) {
            cache.invalidate(redisKey);
        }
    }

}
